use std::fmt;

use cosmwasm_std::{Api, Extern, HumanAddr, Querier, StdError, StdResult, Storage, Uint128};
use cosmwasm_storage::{Bucket, ReadonlyBucket, ReadonlySingleton, Singleton};
use hermit_toolkit_hmip21::asset::AssetInfo;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

pub const CONFIG_KEY: &[u8] = b"config";
pub const POOL_KEY: &[u8] = b"tokenpools";
static POOLS_MAP: &[u8] = b"pool_map";
pub const TR_FEES_KEY: &[u8] = b"tr_fees_key";

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct ConfigInfo {
    pub owner: HumanAddr,
    pub lp_token_code_id: u64,
    pub lp_token_code_hash: String,
    pub protocol_fee_recipient: HumanAddr,
    pub dao_fee_recipient: HumanAddr,
    pub protocol_fee_percent: u64,
    pub dao_fee_percent: u64,
    pub lp_fee_percent: u64,
    pub lp_total_supply: Uint128,
}

pub fn store_config<S: Storage>(storage: &mut S, data: &ConfigInfo) -> StdResult<()> {
    Singleton::new(storage, CONFIG_KEY).save(data)
}

pub fn read_config<S: Storage>(storage: &S) -> StdResult<ConfigInfo> {
    ReadonlySingleton::new(storage, CONFIG_KEY).load()
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct PoolInfo {
    pub token_a_asset: AssetInfo,
    pub token_a_reserve: Uint128,
    pub token_b_asset: AssetInfo,
    pub token_b_reserve: Uint128,
    pub lp_token_asset: AssetInfo,
    pub creator: HumanAddr,
    pub create_block: u64,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub enum FeesType {
    In,
    Out,
}

impl fmt::Display for FeesType {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            FeesType::In => write!(f, "in"),
            FeesType::Out => write!(f, "out"),
        }
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct PoolsMap {
    pub token_a_address: HumanAddr,
    pub token_b_address: HumanAddr,
}

pub fn store_pools_map<S: Storage>(storage: &mut S, data: &Vec<PoolsMap>) -> StdResult<()> {
    Singleton::new(storage, POOLS_MAP).save(data)
}

pub fn read_pools_map<S: Storage>(storage: &S) -> StdResult<Vec<PoolsMap>> {
    ReadonlySingleton::new(storage, POOLS_MAP).load()
}

pub fn store_pool<S: Storage>(
    storage: &mut S,
    token_a_address: &HumanAddr,
    token_b_address: &HumanAddr,
    data: &PoolInfo,
    is_add: bool,
) -> StdResult<()> {
    let key = &[
        token_a_address.to_string().as_bytes(),
        token_b_address.to_string().as_bytes(),
    ]
    .concat();
    let mut pool_bucket: Bucket<S, PoolInfo> = Bucket::new(POOL_KEY, storage);
    pool_bucket.save(key, &data)?;

    if is_add {
        let mut pools_map: Vec<PoolsMap> = vec![];
        let pools_map_f = read_pools_map(storage);
        if pools_map_f.is_ok() {
            pools_map = pools_map_f.unwrap();
        }
        pools_map.push(PoolsMap {
            token_a_address: token_a_address.clone(),
            token_b_address: token_b_address.clone(),
        });
        store_pools_map(storage, &pools_map)?;
    }

    Ok(())
}

pub fn read_pools<S: Storage, A: Api, Q: Querier>(
    deps: &Extern<S, A, Q>,
) -> StdResult<Vec<PoolInfo>> {
    let pools_map = read_pools_map(&deps.storage)?;
    let iter = pools_map.iter();
    let mut pools = vec![];
    for pool_map in iter.clone() {
        // let pool_map = iter.next();
        // if pool_map.is_none() {
        //     break;
        // }
        if read_pool_by_address(
            &deps.storage,
            &pool_map.token_a_address,
            &pool_map.token_b_address,
        )
        .is_ok()
        {
            pools.push(read_pool_by_address(
                &deps.storage,
                &pool_map.token_a_address,
                &pool_map.token_b_address,
            )?)
        }
        if read_pool_by_address(
            &deps.storage,
            &pool_map.token_b_address,
            &pool_map.token_a_address,
        )
        .is_ok()
        {
            pools.push(read_pool_by_address(
                &deps.storage,
                &pool_map.token_b_address,
                &pool_map.token_a_address,
            )?)
        }
    }

    Ok(pools)
}

pub fn read_pool_by_address<S: Storage>(
    storage: &S,
    token_a_address: &HumanAddr,
    token_b_address: &HumanAddr,
) -> StdResult<PoolInfo> {
    let pool_bucket: ReadonlyBucket<S, PoolInfo> = ReadonlyBucket::new(POOL_KEY, storage);
    let key = &[
        token_a_address.to_string().as_bytes(),
        token_b_address.to_string().as_bytes(),
    ]
    .concat();

    match pool_bucket.load(key) {
        Ok(v) => Ok(v),
        Err(_e) => Err(StdError::generic_err("no pool data stored")),
    }
}

pub fn set_tr_fees<S: Storage>(
    storage: &mut S,
    token_contract: &HumanAddr,
    fees_type: FeesType,
    fees: u64,
) -> StdResult<()> {
    let key = &[
        token_contract.to_string().as_bytes(),
        fees_type.to_string().as_bytes(),
    ]
    .concat();
    let mut fees_bucket: Bucket<S, u64> = Bucket::new(TR_FEES_KEY, storage);
    fees_bucket.save(key, &fees)?;

    Ok(())
}

pub fn read_tr_fees<S: Storage>(
    storage: &S,
    token_contract: &HumanAddr,
    fees_type: FeesType,
) -> u64 {
    let fees_bucket: ReadonlyBucket<S, u64> = ReadonlyBucket::new(TR_FEES_KEY, storage);
    let key = &[
        token_contract.to_string().as_bytes(),
        fees_type.to_string().as_bytes(),
    ]
    .concat();

    match fees_bucket.load(key) {
        Ok(v) => v,
        Err(_) => 0u64,
    }
}
